/**
 * @description 用户 controller
 */
const _ = require('lodash');
const dayjs = require('dayjs')
const signToken = require('../utils/signToken')
const { SuccessModel, ErrorModel } = require('../model/ResModel')
const { formatUser } = require('../services/_format')
const doCrypto = require('../utils/crypto')
const { getCirclesCount } = require('./circles')
const {
  getUserInfo,
  createUserInst,
  updateBasicInfo,
} = require('../services/user')
const {
  getMemberInfo,
  updateMemberInfo,
} = require('../services/member')
const { getExercise } = require('../controller/exercise')
const {
  registerUserAccountExistInfo,
  createUserFailInfo,
  loginFailInfo,
  clockInFailInfo,
  updatePersonalInfoFailInfo,
  getPersonalInfoFailInfo,
} = require('../model/ErrorInfo')
const { getRandomInt } = require('../utils/math')

/**
 * 检测用户是否已存在
 * @param {string} account 用户名（电话号）
 */
async function isExist(account) {
  const userInfo = await getUserInfo({account})
  // 该用户已存在
  if (userInfo !== null) { return new ErrorModel(registerUserAccountExistInfo) }
  // 该用户不存在，可添加
  return new SuccessModel()
}

/**
 * 用户注册
 * @param {string} account 账号
 * @param {string} password 密码
 */
async function register(account, password) {
  const userInfo = await getUserInfo({account})
  // 该用户已存在
  if (userInfo !== null) { return new ErrorModel(registerUserAccountExistInfo) }

  try {
    const userInst = await createUserInst(account, password)
    // 创建会员表打卡初始化信息
    userInst.createMember({
      clockCount: Math.floor(Math.random() * 51) // 初始打卡次数是 0~50 的随机数
    });
    return new SuccessModel(userInst.dataValues)
  } catch(ex) {
    console.error(ex.message, ex.stack)
    return new ErrorModel(createUserFailInfo)
  }
}

/**
 * 用户登录
 * @param {object} ctx Koa2 的 ctx
 * @param {string} account 用户名
 * @param {string} password 密码
 * @returns {object} 个人信息对象
 */
async function login(ctx, account, password) {
  const userInfo = await getUserInfo({
    account,
    password: doCrypto(password)
  })
  // 登录失败
  if (userInfo === null) return new ErrorModel(loginFailInfo)
  // 登录成功 - 设置 Token
  const token = signToken(userInfo)

  Object.assign(userInfo, { token })
  return new SuccessModel(userInfo)
}

/**
 * 修改当前用户的个人信息
 * @param {object} ctx ctx
 * @param {object} patchData 要修改的字段数据。{ nickName, gender, birthday, imgUrl, provinceId, cityId, sign }
 */
async function changeInfo(ctx, patchData) {
  const { id } = ctx.state.tokenPayload
  const { nickName, gender, birthday, imgUrl, provinceId, cityId, sign } = patchData;

  // 过滤校验要更新的字段
  const updateData = {};
  if (_.isString(nickName)) { updateData.nickName = nickName }
  if (_.isNumber(gender)) { updateData.gender = gender }
  if (_.isString(birthday)) { updateData.birthday = birthday }
  if (_.isString(imgUrl)) { updateData.imgUrl = imgUrl }
  if (_.isNumber(provinceId)) { updateData.provinceId = provinceId }
  if (_.isNumber(cityId)) { updateData.cityId = cityId }
  if (_.isString(sign)) { updateData.sign = sign }

  // 修改个人基本信息 - 仅在有修改字段时才修改
  if (!_.isEmpty(updateData)) {
    const updateRes = await updateBasicInfo(updateData, { id })
    if (!updateRes) { return new ErrorModel(updatePersonalInfoFailInfo) }
  }

  Object.assign(ctx.state.tokenPayload, updateData) // 更新当前用户的 tokenPayload
  return new SuccessModel({ token: signToken(ctx.state.tokenPayload) })
}

// 获取当前用户的个人信息
async function getInfo(ctx) {
  const { id } = ctx.state.tokenPayload

  const userInfo = await getUserInfo(id)
  if (userInfo === null) { return new ErrorModel(getPersonalInfoFailInfo) }

  // 获取运动数据
  const sportData = await getExercise(ctx);

  // 获取圈子动态总条数
  const circlesCount = await getCirclesCount();

  const formatted = formatUser(userInfo);

  return new SuccessModel({
    ...formatted,
    ranking: getRandomInt(1, 2000), // 排名
    badges: getRandomInt(1, 8), // 徽章数
    duration: sportData.data.sum7Duration,
    meter: sportData.data.sum7Meter,
    calorie: sportData.data.sum7Calorie,
    lastClockDate: formatted['member.lastClockDate'],
    clockCount: formatted['member.clockCount'],
    circlesCount, // 圈子动态数
  })
}

// 进行打卡
async function clockIn(ctx) {
  const { id } = ctx.state.tokenPayload;
  // 先获取会员表该用户打卡信息
  const memberRes = await getMemberInfo(id);

  // 如果无法获取打卡记录，打卡失败
  if (memberRes === null) { return new ErrorModel({ errno: -1, message: '打卡失败，无法获取该用户打卡信息' }) }

  const { lastClockDate, clockCount } = memberRes;

  // 如果今日已打卡，打卡失败
  if (lastClockDate !== null && dayjs().isSame(lastClockDate, 'day')) {
    return new ErrorModel({ errno: -1, message: '打卡失败，今日已打卡' })
  }

  // 更新打卡信息
  const updateData = {
    lastClockDate: dayjs().format('YYYY-MM-DD'),
    clockCount: clockCount + 1,
  }
  const result = await updateMemberInfo(updateData, id)
  if (result === false) { return new ErrorModel(clockInFailInfo) }

  return new SuccessModel()
}

// 获取用户的打卡信息
async function getClockInInfo(ctx) {
  const { id } = ctx.state.tokenPayload;
  const memberRes = await getMemberInfo(id);

  if (memberRes === null) { return new ErrorModel({ errno: -1, message: '无法获取当前用户打卡信息' }) }

  const { lastClockDate, clockCount } = memberRes;
  return new SuccessModel({
    // 今日是否已打卡
    isClockIn: lastClockDate === null
      ? false
      : dayjs().isSame(lastClockDate, 'day')
        ? true
        : false,
    clockCount, // 打卡次数
  });
}

module.exports = {
  isExist,
  register,
  login,
  clockIn,
  getClockInInfo,
  changeInfo,
  getInfo,
}
